package com.delicaterock.mpw;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.SocketTimeoutException;
import java.util.Enumeration;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.Timer;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;

import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser;

public class Main {
	private HWND hWnd;
	private JFrame f = null;
	private Robot robot;
	private int port = 4444;
	private int PICH = 180;
	private int PICW = 180;
	private long secret;
	private int TIMEOUT = 30000;
	private Timer timer;
	private int expire = TIMEOUT/1000;
	private JProgressBar pb;
	
	public static void main(String[] args) {
		Main ins = new Main();
		if (ins.testRegisterHotKey()) {
			while (true) {
				System.out.println("*********************************");
				System.out.println("Mobile Password Wallet Server 1.0");
				System.out.println("    2011 www.delicaterock.com    ");
				System.out.println("*********************************");
				System.out.println("[Information] Waiting for the hot key(Alt+M) pressed.");
				WinUser.MSG msg = ins.waitForMessage(10000);
				ins.mainTask(msg);
			}
		} else {
			System.out.println("[Error] RegisterHotKey failed.");
		}
	}
	
	private String formatURL(String ips, String title) {
		if (ips==null || title==null) {
			return null;
		}
		String ipStr = ips;
		if (ips.endsWith(",")) {
			ipStr = ips.substring(0, ips.length()-1);
		}		
		String titleStr = title.replaceAll("[^A-Za-z0-9]", "_");
		secret = System.currentTimeMillis();
		return String.format("mpw://%s:%d@%d/%s", ipStr, port, secret, titleStr);
	}
	
	private void mainTask(WinUser.MSG msg) {
		if (msg!=null) {
			try {
				// Get the active window title
				String title = getForegroundWindowTitle();
				if (title==null || "".equalsIgnoreCase(title)) {
					System.out.println("[Warning] The target window title is empty.");
					return;
				}
				System.out.println(String.format("[Information] Window(%s) is selected.",title));
				
				// Get the host IPs
				String ips = getAllIPs();
				if (ips==null) {
					System.out.println("[Error] Can't get any IP address of the host.");
					return;
				}
				
				// Compose ID for the application
				String url = formatURL(ips, title);
				if (null==url || "".equalsIgnoreCase(url)) {
					System.out.println("[Error] Can't compose the application ID.");
					return;
				}
				System.out.println("[Debug] The application ID is "+url);
				
				// Create QR barcode and show it
			    BitMatrix matrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE,
			            PICW, PICH);
				BufferedImage image = toBufferedImage(matrix);
				showImage(image);
				System.out.println("[Information] Please scan the barcode using our application on your phone.");
				
				if (title.contains("PowerPoint")) {
					// Let's control the PowerPoint
					playPowerPoint();
				} else {
					// Wait for authentication sent from the phone
					String line = receiveAuthentication();
					if (f!=null) {
						f.setVisible(false);
					}
					if (line ==null || "".equalsIgnoreCase(line)) {
						System.out.println("[Error] An invalid authentication received. Please restart.");				
						return;
					}				
					
					sendKeysToWindow(hWnd, line);
					System.out.println("[Information] Done.\n");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}
	
	private String checkAuth(String line) {
		if (null==line) {
			return null;
		} else {
			// e.g. [mpw:1234566]user/tpassword
			if (line.startsWith("[mpw:") && line.contains(String.valueOf(secret))) {
				return line.substring(line.lastIndexOf("]")+1, line.length());
			} else {
				//return null;
				return line.substring(line.lastIndexOf("]")+1, line.length());
			}
		}
	}
	
	public void playPowerPoint() {
		ServerSocket serverSocket = null;
		Socket clientSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			serverSocket.setSoTimeout(TIMEOUT);
		    System.out.println("[Information] The host is listening on TCP port 4444 for incoming message...");
		    System.out.println(String.format("[Information] This sesson will expire in %d seconds.", TIMEOUT/1000));
		    clientSocket = serverSocket.accept();
		    if (clientSocket != null) {
			    BufferedReader in = new BufferedReader(
	                    new InputStreamReader(
	                        clientSocket.getInputStream(), "utf8"));
			    String inputLine;
			    inputLine = in.readLine();
			    while (inputLine!=null) {
			    	System.out.println("[Debug] Received message "+inputLine);
			    	if (f!=null) {
			    		f.setVisible(false);
			    	}
			    	String cmd = checkAuth(inputLine);
			    	if (cmd==null) {
			    		System.out.println("[Error] Authentication failed.");
			    		inputLine = null;
			    	} else if (cmd.contains("next")) {
			    		sendKeysToWindow(hWnd, "n");
			    		inputLine = in.readLine();
			    	} else if (cmd.contains("previous")){
			    		sendKeysToWindow(hWnd, "p");
			    		inputLine = in.readLine();
			    	} else {
			    		inputLine = null;
			    	}			    				    	
			    }
			    clientSocket.close();
			    serverSocket.close();
		    }
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public String receiveAuthentication() {
		ServerSocket serverSocket = null;
		Socket clientSocket = null;
		try {
		    serverSocket = new ServerSocket(port);
		    serverSocket.setSoTimeout(TIMEOUT);		    
		    System.out.println("[Information] The host is listening on TCP port 4444 for incoming authentication...");
		    System.out.println(String.format("[Information] This sesson will expire in %d seconds.", TIMEOUT/1000));
		    clientSocket = serverSocket.accept();
		    if (clientSocket != null) {
			    BufferedReader in = new BufferedReader(
	                    new InputStreamReader(
	                        clientSocket.getInputStream(), "utf8"));
			    String inputLine;
			    inputLine = in.readLine();
			    clientSocket.close();
			    serverSocket.close();
			    System.out.println("[Debug] Received authentication ");		    
			    return checkAuth(inputLine);
		    } else {		    	
		    	serverSocket.close();
		    	return null;
		    }
		} catch (SocketTimeoutException se) {
			try {
				if (null != f) {
					f.setVisible(false);
				}
				if (null != serverSocket) {
					serverSocket.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("[Warning] Time out for receiving an authentication");
			return null;
		} catch (IOException e) {
			try {
				if (null != serverSocket) {
					serverSocket.close();
				}
			} catch (IOException e2) {
				// TODO Auto-generated catch block
				e2.printStackTrace();
			}			
		    e.printStackTrace();
		    return null;
		}
	}
  
	public String getAllIPs() {
		try {
			Enumeration<NetworkInterface> theIntfList = NetworkInterface.getNetworkInterfaces();
			Enumeration<InetAddress> theAddrList = null;
			NetworkInterface theIntf = null;
			InetAddress theAddr = null;
			String ips = "";
			while(theIntfList.hasMoreElements())
			{
				theIntf = theIntfList.nextElement();
				theAddrList = theIntf.getInetAddresses();
				while(theAddrList.hasMoreElements()) {
					InetAddress curIP = theAddrList.nextElement();
					if (curIP instanceof Inet4Address && !curIP.isLoopbackAddress()) {
						ips += curIP.getHostAddress()+",";
					}
				}
			}		  
			return ips;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
  
  public void showImage(BufferedImage image) {
	  	if (null == f) {
	  		f = new JFrame("Mobile Password Wallet");
	  	} else {	  		
	  		f.dispose();	  		
	  		f = new JFrame("Mobile Password Wallet");
	  	}
	  	LoadImage img = new LoadImage(image);
	  	JLabel label = new JLabel("Scan using our app. on your phone...");
	  	JPanel panel = new JPanel();
	  	if (pb==null) {
	  		pb = new JProgressBar(0,TIMEOUT/1000);
	  	}
	  	panel.setLayout(new BorderLayout());
	  	panel.add(label, BorderLayout.NORTH);
	  	panel.add(pb, BorderLayout.SOUTH);
	  	panel.add(img, BorderLayout.CENTER);	  	
	  	 
		f.add(panel);		
		f.pack();
		f.setVisible(true);
		//f.setSize(img.getWidth(), img.getHeight());
		f.setLocation(0, 0);
		f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		
	    timer = new Timer(1000, new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent e) {
				if (expire>0) {
					pb.setValue(expire);
					expire--;
				} else {
					timer.stop();
					f.dispose();
				}
			}});
	    expire = TIMEOUT/1000;
	    timer.start();
		
  }
  
  public void sendKeysToWindow(HWND win, String line) throws AWTException {
	  System.out.println("[Information] Sending your authentication to the target window.");

	  if (User32.INSTANCE.SetForegroundWindow(win)) {
		  try {
			Thread.sleep(600);
		  } catch (InterruptedException e) {
			e.printStackTrace();
		  }
		  robot = new Robot();
		  for (int i=0; i<line.length(); i++) {
			  //System.out.println(line.charAt(i));
			  type(line.charAt(i));
		  }
	  } else {
		  System.out.println("[Error] SetForegroundWindow failed.");
	  }
  }
  
  public void type(char character) {
	  //System.out.println("type:"+(int)character);
      switch (character) {
      case 'a': doType(KeyEvent.VK_A); break;
      case 'b': doType(KeyEvent.VK_B); break;
      case 'c': doType(KeyEvent.VK_C); break;
      case 'd': doType(KeyEvent.VK_D); break;
      case 'e': doType(KeyEvent.VK_E); break;
      case 'f': doType(KeyEvent.VK_F); break;
      case 'g': doType(KeyEvent.VK_G); break;
      case 'h': doType(KeyEvent.VK_H); break;
      case 'i': doType(KeyEvent.VK_I); break;
      case 'j': doType(KeyEvent.VK_J); break;
      case 'k': doType(KeyEvent.VK_K); break;
      case 'l': doType(KeyEvent.VK_L); break;
      case 'm': doType(KeyEvent.VK_M); break;
      case 'n': doType(KeyEvent.VK_N); break;
      case 'o': doType(KeyEvent.VK_O); break;
      case 'p': doType(KeyEvent.VK_P); break;
      case 'q': doType(KeyEvent.VK_Q); break;
      case 'r': doType(KeyEvent.VK_R); break;
      case 's': doType(KeyEvent.VK_S); break;
      case 't': doType(KeyEvent.VK_T); break;
      case 'u': doType(KeyEvent.VK_U); break;
      case 'v': doType(KeyEvent.VK_V); break;
      case 'w': doType(KeyEvent.VK_W); break;
      case 'x': doType(KeyEvent.VK_X); break;
      case 'y': doType(KeyEvent.VK_Y); break;
      case 'z': doType(KeyEvent.VK_Z); break;
      case 'A': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_A); break;
      case 'B': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_B); break;
      case 'C': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_C); break;
      case 'D': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_D); break;
      case 'E': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_E); break;
      case 'F': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_F); break;
      case 'G': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_G); break;
      case 'H': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_H); break;
      case 'I': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_I); break;
      case 'J': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_J); break;
      case 'K': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_K); break;
      case 'L': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_L); break;
      case 'M': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_M); break;
      case 'N': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_N); break;
      case 'O': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_O); break;
      case 'P': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_P); break;
      case 'Q': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_Q); break;
      case 'R': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_R); break;
      case 'S': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_S); break;
      case 'T': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_T); break;
      case 'U': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_U); break;
      case 'V': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_V); break;
      case 'W': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_W); break;
      case 'X': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_X); break;
      case 'Y': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_Y); break;
      case 'Z': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_Z); break;
      case '`': doType(KeyEvent.VK_BACK_QUOTE); break;
      case '0': doType(KeyEvent.VK_0); break;
      case '1': doType(KeyEvent.VK_1); break;
      case '2': doType(KeyEvent.VK_2); break;
      case '3': doType(KeyEvent.VK_3); break;
      case '4': doType(KeyEvent.VK_4); break;
      case '5': doType(KeyEvent.VK_5); break;
      case '6': doType(KeyEvent.VK_6); break;
      case '7': doType(KeyEvent.VK_7); break;
      case '8': doType(KeyEvent.VK_8); break;
      case '9': doType(KeyEvent.VK_9); break;
      case '-': doType(KeyEvent.VK_MINUS); break;
      case '=': doType(KeyEvent.VK_EQUALS); break;
      case '~': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_BACK_QUOTE); break;
      case '!': doType(KeyEvent.VK_EXCLAMATION_MARK); break;
      case '@': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_2); break;
      case '#': doType(KeyEvent.VK_NUMBER_SIGN); break;
      case '$': doType(KeyEvent.VK_DOLLAR); break;
      case '%': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_5); break;
      case '^': doType(KeyEvent.VK_CIRCUMFLEX); break;
      case '&': doType(KeyEvent.VK_AMPERSAND); break;
      case '*': doType(KeyEvent.VK_ASTERISK); break;
      case '(': doType(KeyEvent.VK_LEFT_PARENTHESIS); break;
      case ')': doType(KeyEvent.VK_RIGHT_PARENTHESIS); break;
      case '_': doType(KeyEvent.VK_UNDERSCORE); break;
      case '+': doType(KeyEvent.VK_PLUS); break;
      case '\t': 
    	  doType(KeyEvent.VK_TAB); 
    	  try {
			Thread.sleep(1000);	//IE browser needs waiting
    	  } catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
    	  }
    	  break;
      case '\n': doType(KeyEvent.VK_ENTER); break;
      case '[': doType(KeyEvent.VK_OPEN_BRACKET); break;
      case ']': doType(KeyEvent.VK_CLOSE_BRACKET); break;
      case '\\': doType(KeyEvent.VK_BACK_SLASH); break;
      case '{': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_OPEN_BRACKET); break;
      case '}': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_CLOSE_BRACKET); break;
      case '|': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_BACK_SLASH); break;
      case ';': doType(KeyEvent.VK_SEMICOLON); break;
      case ':': doType(KeyEvent.VK_COLON); break;
      case '\'': doType(KeyEvent.VK_QUOTE); break;
      case '"': doType(KeyEvent.VK_QUOTEDBL); break;
      case ',': doType(KeyEvent.VK_COMMA); break;
      case '<': doType(KeyEvent.VK_LESS); break;
      case '.': doType(KeyEvent.VK_PERIOD); break;
      case '>': doType(KeyEvent.VK_GREATER); break;
      case '/': doType(KeyEvent.VK_SLASH); break;
      case '?': doType(KeyEvent.VK_SHIFT, KeyEvent.VK_SLASH); break;
      case ' ': doType(KeyEvent.VK_SPACE); break;
      default:
              throw new IllegalArgumentException("Cannot type character " + character);
      }
  }
  
  private void doType(int... keyCodes) {
      doType(keyCodes, 0, keyCodes.length);
  }

  private void doType(int[] keyCodes, int offset, int length) {
      if (length == 0) {
              return;
      }

      robot.keyPress(keyCodes[offset]);
      doType(keyCodes, offset + 1, length - 1);
      robot.keyRelease(keyCodes[offset]);
  }
  	
  public static BufferedImage toBufferedImage(BitMatrix matrix) {
		    int width = matrix.getWidth();
		    int height = matrix.getHeight();
		    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		    for (int x = 0; x < width; x++) {
		      for (int y = 0; y < height; y++) {
		        image.setRGB(x, y, matrix.get(x, y) ? 0 : 0xFFFFFF);
		      }
		    }
		    return image;	    
	  }
	
    public boolean testRegisterHotKey() {
        int vk = KeyEvent.VK_M;
        int id = 1;
        return User32.INSTANCE.RegisterHotKey(null, id, User32.INSTANCE.MOD_ALT, vk);
    }
    
    private WinUser.MSG waitForMessage(int timeout) {
    	System.out.println("[Information] Select the target window, move the cursor to the start field, and press Alt+M when ready.");
        WinUser.MSG msg = new WinUser.MSG();

        try {
            long time = System.currentTimeMillis();
            while (true) {
            	//System.out.println("Wait for HotKey...");
                while (User32.INSTANCE.PeekMessage(msg, null, 0, 0, 1)) {
                    if (msg.message == User32.INSTANCE.WM_HOTKEY) {
                        return msg;
                    }
                }
                //if (System.currentTimeMillis() - time > timeout)
                //    break;

                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }	
    
	private String getForegroundWindowTitle() {
		hWnd = User32.INSTANCE.GetForegroundWindow();
		int len = User32.INSTANCE.GetWindowTextLength(hWnd);
		char[] title = new char[4*len];
		len = User32.INSTANCE.GetWindowText(hWnd, title, len*4);
		if (len>0) {
			return String.copyValueOf(title,0,len);
		} else {
			return null;
		}		
	}
	
	private class LoadImage extends Component {
        
	    BufferedImage img;

	    public LoadImage(BufferedImage img) {
	    	this.img = img;
	    }
	    
	    public void paint(Graphics g) {
	        g.drawImage(img, 0, 0, null);
	    }

	    public Dimension getPreferredSize() {
	        if (img == null) {
	             return new Dimension(170,170);
	        } else {
	           return new Dimension(img.getWidth(null), img.getHeight(null));
	       }
	    }
	}	
}
